namespace ScrumSprintMonitor.Samples.Snapshooter
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.ComponentModel.Composition;
	using System.Diagnostics;
	using System.IO;
	using System.Linq;
	using System.Threading;
	using System.Windows;
	using System.Windows.Media;
	using System.Windows.Media.Imaging;

	using Extensibility;
	using Extensibility.Events;

	using Infrastructure;

	using Microsoft.Practices.Composite.Events;

	#endregion

	[Export(typeof(ScrumSprintMonitorAddIn))]
	[AddIn(Name = "Snapshooter")]
	public class SnapshooterAddIn : ScrumSprintMonitorAddIn
	{
		private readonly List<Window> capturedWindowsList = new List<Window>();
		private IAddInHost<SnapshooterAddIn> addInHost;
		private ILogger<SnapshooterAddIn> logger;
		private SynchronizationContext syncContext;
		private Timer timer;

		#region Overrides of ScrumSprintMonitorAddIn

		public override void Load()
		{
			this.logger = ServiceProvider.GetService<ILogger<SnapshooterAddIn>>();

			this.syncContext = SynchronizationContext.Current;
			this.timer = new Timer(OnTimer, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10));

			var eventAggregator = ServiceProvider.GetService<IEventAggregator>();
			eventAggregator.GetEvent<MonitorWindowShownEvent>().Subscribe(OnMonitorWindowShown);
		}

		public override void Unload()
		{
			this.timer.Dispose();
		}

		#endregion

		private void OnMonitorWindowShown(Window monitorWindow)
		{
			this.capturedWindowsList.Add(monitorWindow);
		}

		private void OnTimer(object state)
		{
			this.syncContext.Post(RenderToPng, state);
		}

		private void RenderToPng(object state)
		{
			if (!this.capturedWindowsList.Any())
			{
				this.logger.Debug("No monitor windows registered to capture.", this.capturedWindowsList.Count);

				return;
			}

			this.logger.Debug("Capturing {0} monitor windows...", this.capturedWindowsList.Count);

			var bitmapSize = new Size(this.capturedWindowsList.Sum(w => w.ActualWidth), this.capturedWindowsList.Max(w => w.ActualHeight));
			var bmp = new WriteableBitmap((int) bitmapSize.Width, (int) bitmapSize.Height, 96, 96, PixelFormats.Pbgra32, null);
			var maxMonitorSize = new Size(this.capturedWindowsList.Max(w => w.ActualWidth), this.capturedWindowsList.Max(w => w.ActualHeight));
			var sourcePixelsArray = new byte[4 * (uint) (maxMonitorSize.Width * maxMonitorSize.Height)];
			int sourceStride = (int) ((maxMonitorSize.Width * 32 + 7) / 8);

			int x = 0;
			foreach (var monitorWindow in this.capturedWindowsList)
			{
				var monitorBmp = new RenderTargetBitmap((int) monitorWindow.ActualWidth, (int) monitorWindow.ActualHeight, 96, 96, PixelFormats.Pbgra32);
				monitorBmp.Render(monitorWindow);

				monitorBmp.CopyPixels(sourcePixelsArray, sourceStride, 0);
				bmp.WritePixels(new Int32Rect(x, 0, monitorBmp.PixelWidth, monitorBmp.PixelHeight), sourcePixelsArray, sourceStride, 0);

				x += monitorBmp.PixelWidth;
			}

			var png = new PngBitmapEncoder();
			string filePath = GetNextBitmapFilePath();

			png.Frames.Add(BitmapFrame.Create(bmp));

			using (Stream stm = File.Create(filePath))
			{
				png.Save(stm);

				this.logger.Debug("Captured monitor windows to {0}.", filePath);
			}
		}

		private string GetNextBitmapFilePath()
		{
			if (this.addInHost == null)
			{
				this.addInHost = ServiceProvider.GetService<IAddInHost<SnapshooterAddIn>>();
			}

			string fileName = Path.Combine(this.addInHost.GetOutputDirectory(), string.Format("Capture_{0:s}.png", DateTime.Now).Replace(':', '_'));
			Debug.Assert(!File.Exists(fileName));
			return fileName;
		}
	}
}